﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using SlimDX;
using SlimDX.Windows;
using Common;

namespace Editor
{
    internal partial class DirectXView : UserControl
    {
        public enum ViewType
        {
            Perspective,
            Left,
            Right,
            Front,
            Back,
            Top,
            Bottom
        }

        private EditorDocument m_Document;
        private SlimDX.Direct3D9.Device m_Device;
        private SlimDX.Direct3D9.SwapChain m_SwapChain;
        private SlimDX.Direct3D9.Surface m_DepthStencil;
        private Camera m_Camera = new Camera();
        private GridMesh m_GridMesh = new GridMesh();
        private ViewType m_ViewType = ViewType.Perspective;
        private float m_InitialDistance = 256.0f;
		private float m_ZoomAmount = 1.0f;
        private String m_Text = "";

        [Browsable(false)]
        public SlimDX.Direct3D9.SwapChain SwapChain
        {
            get { return m_SwapChain; }
        }

        [Browsable(false)]
        public SlimDX.Direct3D9.Surface DepthStencil
        {
            get { return m_DepthStencil; }
        }

        [Browsable(false)]
        public Camera Camera
        {
            get { return m_Camera; }
        }

        [Browsable(false)]
        public Matrix ViewMatrix
        {
            get
            {
                return m_Camera.Transform;
            }
        }

        [Browsable(false)]
        public Matrix ProjectionMatrix
        {
            get
            {
                float fov = (float)Math.PI / 3.0f;
                float aspect = (float)ClientSize.Width / (float)ClientSize.Height;
                Matrix matProj;
                if (Type == ViewType.Perspective)
                    matProj = SlimDX.Matrix.PerspectiveFovRH(fov, aspect, 0.1f, 4096.0f);
                else
					matProj = SlimDX.Matrix.OrthoRH(ClientSize.Width / m_ZoomAmount, ClientSize.Height / m_ZoomAmount, 0.1f, 4096.0f);
                return matProj;
            }
        }

        [Browsable(false)]
        public Matrix ViewProjectionMatrix
        {
            get
            {
                Matrix matViewProjection = Matrix.Multiply(ViewMatrix, ProjectionMatrix);
                return matViewProjection;
            }
        }

        [Browsable(false)]
        public GridMesh GridMesh
        {
            get { return m_GridMesh; }
        }

        [Category("View")]
        [Bindable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public override String Text
        {
            get { return m_Text; }
            set { m_Text = value; }
        }

        [Category("View")]
        public ViewType Type
        {
            get { return m_ViewType; }
            set { m_ViewType = value; }
        }

        [Category("View")]
        public float InitialDistance
        {
            get { return m_InitialDistance; }
            set { m_InitialDistance = value; }
        }

        public DirectXView()
        {
            ResizeRedraw = true;

            this.CreateParams.ClassStyle |= (int)(NativeMethods.ClassStyles.CS_HREDRAW | NativeMethods.ClassStyles.CS_VREDRAW);
            SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.ResizeRedraw, true);

            InitializeComponent();
        }

        public DirectXView(EditorDocument document)
            : this()
        {
            InitializeView(document);
        }

        public void InitializeView(EditorDocument document)
        {
            m_Document = document;

            //
            m_Camera.WorldUpAxis = Properties.Settings.Default.ZAxisUp ? Camera.Axis.ZAxis : Camera.Axis.YAxis;

            Vector3 cameraPos;
            Vector3 cameraTarget = Vector3.Zero;
            switch (m_ViewType)
            {
                case ViewType.Perspective:
                    cameraPos = new Vector3(1, 1, 1);
                    cameraPos.Normalize();
                    cameraPos *= InitialDistance;
                    break;
                case ViewType.Left:
                    cameraPos = new Vector3(-InitialDistance, 0, 0);
                    break;
                case ViewType.Right:
                    cameraPos = new Vector3(InitialDistance, 0, 0);
                    break;
                case ViewType.Front:
                    if (m_Camera.WorldUpAxis == Camera.Axis.YAxis)
                        cameraPos = new Vector3(0, 0, InitialDistance);
                    else
                        cameraPos = new Vector3(0, InitialDistance, 0);
                    break;
                case ViewType.Back:
                    if (m_Camera.WorldUpAxis == Camera.Axis.YAxis)
                        cameraPos = new Vector3(0, 0, -InitialDistance);
                    else
                        cameraPos = new Vector3(0, -InitialDistance, 0);
                    break;
                case ViewType.Top:
                    if (m_Camera.WorldUpAxis == Camera.Axis.YAxis)
                        cameraPos = new Vector3(0, InitialDistance, 0);
                    else
                        cameraPos = new Vector3(0, 0, InitialDistance);
                    break;
                case ViewType.Bottom:
                    if (m_Camera.WorldUpAxis == Camera.Axis.YAxis)
                        cameraPos = new Vector3(0, -InitialDistance, 0);
                    else
                        cameraPos = new Vector3(0, 0, -InitialDistance);
                    break;
                default:
                    cameraPos = new Vector3(1, 1, 1);
                    break;
            }

            m_Camera.Position = cameraPos;
            m_Camera.Target = cameraTarget;
            m_Camera.Locked = (m_ViewType != ViewType.Perspective);
			m_ZoomAmount = 1.0f;

            //
            if (!this.DesignMode)
            {
                m_Device = RenderUtils.Direct3DDevice;
                if (m_Device != null)
                {
                    createDeviceObjects();

                    UpdateGrid();
                }
            }
        }

        private void createDeviceObjects()
        {
            if (m_Device != null)
            {
                if (m_SwapChain != null)
                {
                    RenderUtils.DestroySwapChain(m_SwapChain);
                }
                m_SwapChain = RenderUtils.CreateSwapChain(this.ClientSize.Width, this.ClientSize.Height, this.Handle);

                //
                if (m_DepthStencil != null)
                {
                    m_DepthStencil.Dispose();
                }

                m_DepthStencil = SlimDX.Direct3D9.Surface.CreateDepthStencil(m_Device, this.ClientSize.Width, this.ClientSize.Height, SlimDX.Direct3D9.Format.D24S8, SlimDX.Direct3D9.MultisampleType.None, 0, false);
            }
        }

        public List<Entity> GetEntitiesAtPoint(Point point)
        {
            if (m_Document.Entities.Count == 0)
                return new List<Entity>();

            List<Entity> entities = new List<Entity>();
            entities.Add(m_Document.Entities[0]);
            return entities;
        }

        public void Render(float elapsedTime, bool bActive, bool bDragging)
        {
            if (BeginRendering(bActive, bDragging))
            {
                Matrix matView = ViewMatrix;
                Matrix matProjection = ProjectionMatrix;

                GridMesh.Render(m_Device, matView, matProjection);

                foreach (Entity entity in m_Document.Entities)
                {
                    entity.Render(m_Device, matView, matProjection);
                }

                if (!String.IsNullOrEmpty(m_Text))
                {
                    RenderUtils.Direct3DFont.DrawString(null, m_Text, 5, 5, new Color4((bActive) ? Color.White : Color.Black));
                }

                EndRendering();
            }
        }

        private bool BeginRendering(bool bActive, bool bDragging)
        {
            if ((m_Device == null) || (m_SwapChain == null) || (m_DepthStencil == null))
            {
                return false;
            }

            SlimDX.Direct3D9.Surface colorSurface = m_SwapChain.GetBackBuffer(0);
            m_Device.SetRenderTarget(0, colorSurface);

            m_Device.DepthStencilSurface = m_DepthStencil;

            //
            Color4 selCol = new Color4(SystemColors.Highlight);
            Color4 bgCol = new Color4(1.0f, 0.3f, 0.3f, 0.3f);
            int border = 2;
            Rectangle viewRect = new Rectangle(border, border, ClientSize.Width - border * 2, ClientSize.Height - border * 2);
            if (bActive)
            {
                m_Device.Clear(SlimDX.Direct3D9.ClearFlags.All, selCol, 1.0f, 0);
                m_Device.Clear(SlimDX.Direct3D9.ClearFlags.Target, bgCol, 1.0f, 0,
                               new Rectangle[]
                                   {
                                       viewRect
                                   });
            }
            else
            {
                m_Device.Clear(SlimDX.Direct3D9.ClearFlags.All, bgCol, 1.0f, 0);
            }
            m_Device.Viewport = new SlimDX.Direct3D9.Viewport(viewRect.X, viewRect.Y, viewRect.Width, viewRect.Height);

            m_Device.BeginScene();

            return true;
        }

        private void EndRendering()
        {
            if ((m_Device == null) || (m_SwapChain == null))
            {
                return;
            }

            m_Device.EndScene();

            m_SwapChain.Present(SlimDX.Direct3D9.Present.None);
        }

        public void FocusCamera(List<Entity> entities)
        {
            m_Camera.Focus(entities);
            UpdateGrid();
        }

        public void ZoomCamera(float d)
        {
			if (Type == ViewType.Perspective)
				m_Camera.Zoom(d);
			else
				m_ZoomAmount += d / 10.0f;

            UpdateGrid();
        }

        public void MoveCamera(Vector3 csOffset)
        {
            m_Camera.Move(csOffset);
            UpdateGrid();
        }

        public void RotateCamera(Camera.RotateType type, float inclination, float azimuth)
        {
            m_Camera.Rotate(type, inclination, azimuth);

            if (type == Camera.RotateType.AboutTarget)
            {
                UpdateGrid();
            }
        }

        private void UpdateGrid()
        {
            if ((!this.DesignMode) && (m_Device != null))
            {
                double distance = (double)(m_Camera.Position.Length() / m_ZoomAmount);
                double l = Math.Log10(distance);
                double f = Math.Floor(l);
                double gridChunk = (float)Math.Pow(10.0, f);
                float gridSize = (float)Math.Ceiling(Math.Ceiling(distance / gridChunk) * gridChunk / 20.0);
                if ((m_GridMesh.GridSpacing <= 0) || (m_GridMesh.GridSpacing != (int)gridSize))
                {
                    m_GridMesh.GridSpacing = (int)gridSize;

                    GridMesh.Axis gridAxis = GridMesh.Axis.XY;
                    if (m_Camera.WorldUpAxis == Camera.Axis.ZAxis)
                    {
                        if ((m_ViewType == ViewType.Perspective) || (m_ViewType == ViewType.Top) || (m_ViewType == ViewType.Bottom))
                            gridAxis = GridMesh.Axis.XY;
                        else if ((m_ViewType == ViewType.Front) || (m_ViewType == ViewType.Back))
                            gridAxis = GridMesh.Axis.XZ;
                        else if ((m_ViewType == ViewType.Left) || (m_ViewType == ViewType.Right))
                            gridAxis = GridMesh.Axis.YZ;
                    }
                    else
                    {
                        if ((m_ViewType == ViewType.Perspective) || (m_ViewType == ViewType.Top) || (m_ViewType == ViewType.Bottom))
                            gridAxis = GridMesh.Axis.XZ;
                        else if ((m_ViewType == ViewType.Front) || (m_ViewType == ViewType.Back))
                            gridAxis = GridMesh.Axis.XY;
                        else if ((m_ViewType == ViewType.Left) || (m_ViewType == ViewType.Right))
                            gridAxis = GridMesh.Axis.YZ;
                    }
                    m_GridMesh.GridAxis = gridAxis;

                    m_GridMesh.Create(m_Device);
                }
            }
        }

        protected override void OnPaintBackground(PaintEventArgs pe)
        {
            // Should never be called as SlimDX sets the window style "AllPaintingInWmPaint"
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            if (this.DesignMode || (m_SwapChain == null))
            {
                base.OnPaint(e);

                //
                e.Graphics.Clear(SystemColors.InactiveCaption);

                //
                e.Graphics.DrawString(m_Text, SystemFonts.SmallCaptionFont, SystemBrushes.ActiveCaptionText, 5, 5);

                //
                String text = (this.DesignMode) ? "[DirectX]" : ((m_Device != null) ? "[Swap Chain Creation Failed]" : "[Device Creation Failed]");
                SizeF textSize = e.Graphics.MeasureString(text, SystemFonts.DefaultFont);
                PointF textPos = new PointF((this.ClientSize.Width-textSize.Width)/2, (this.ClientSize.Height-textSize.Height)/2);
                //
                e.Graphics.DrawString(text, SystemFonts.DefaultFont, SystemBrushes.InactiveCaptionText, textPos);

                //
                Rectangle borderRect = this.ClientRectangle;
                borderRect.Width--;
                borderRect.Height--;
                e.Graphics.DrawRectangle(SystemPens.InactiveCaptionText, borderRect);
            }
        }

        private void DirectXView_Layout(object sender, LayoutEventArgs e)
        {
            if (this.DesignMode || (m_Device == null))
                return;

            //Control c = sender as Control;
            //System.Diagnostics.Debug.WriteLine(String.Format("Control: {0}\tBounds: {1}\tReason {2}", c.Name, c.Bounds, e.AffectedProperty));

            if (e.AffectedProperty == "Bounds")
            {
                if ((Width > 0) && (Height > 0))
                {
                    createDeviceObjects();
                }
            }
        }
    }
}
